﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Globalization;
using System.Security;
using System.Text;
using System.Text.RegularExpressions;
using Kyoh.Lib.Component.Globalization;

namespace Kyoh.Lib.Component.Traces
{
    /// <summary>トレース出力クラス。</summary>
    public class TraceData : DataRow
    {
        private readonly TraceDataTable _tableTraceData;

        internal TraceData(DataRowBuilder rb) : base(rb) { _tableTraceData = ((TraceDataTable)(Table)); }

        /// <summary>現在出力されている内容を、テキスト文字列として取得します。</summary>
        /// <param name="detail">詳細な内容を含む場合は <c>true</c>。それ以外の場合は <c>false</c>。</param>
        /// <returns>現在出力されている内容。</returns>
        public string GetText(bool detail = false)
        {
            var list = new List<string> {Index.ToString(CultureInfo.InvariantCulture)};
            if(detail)
                list.Add(Id.ToString(CultureInfo.InvariantCulture));
            list.Add(EventType.ToString());
            list.Add(EscapeText(Message));
            if(detail)
                list.Add(EscapeText(Detail));
            list.Add(EscapeText(DateTime));
            if(detail)
                list.Add(EscapeText(Timestamp));
            if (detail)
            {
                list.Add(EscapeText(ProcessId));
                list.Add(EscapeText(ThreadId));
                list.Add(EscapeText(Source));
                list.Add(EscapeText(Callstack));
                list.Add(RelatedActivityId.ToString());
                list.Add(EscapeText(LogicalOperations));
            }

            return string.Join("\t", list.ToArray());
        }

        readonly Regex _escapeText = new Regex(@"\s+", RegexOptions.Compiled);
        private string EscapeText(string text)
        {
            return text == null ? string.Empty : _escapeText.Replace(text, " ");
        }

        /// <summary>現在出力されている内容を、XML文字列として取得します。</summary>
        /// <param name="detail">詳細な内容を含む場合は <c>true</c>。それ以外の場合は <c>false</c>。</param>
        /// <returns>現在出力されている内容。</returns>
        public string GetXml(bool detail = false)
        {
            var builder = new StringBuilder();
            GetXml(builder, detail);
            return builder.ToString();
        }

        /// <summary>現在出力されている内容を、XML文字列として取得します。</summary>
        /// <param name="builder">内容を書き込む <see cref="StringBuilder"/>。</param>
        /// <param name="detail">詳細な内容を含む場合は <c>true</c>。それ以外の場合は <c>false</c>。</param>
        public void GetXml(StringBuilder builder, bool detail = false)
        {
            builder.Append("<Trace>");
            builder.Append("<Index>").Append(Index).Append("</Index>");
            
            if(detail)
                builder.Append("<Id>").Append(Id).Append("</Id>");
            builder.Append("<EventType>").Append(EventType).Append("</EventType>");
            builder.Append("<Message>").Append(SecurityElement.Escape(Message)).Append("</Message>");
            if(detail)
                builder.Append("<Detail>").Append(SecurityElement.Escape(Detail)).Append("</Detail>");
            builder.Append("<DateTime>").Append(SecurityElement.Escape(DateTime)).Append("</DateTime>");
            if(detail)
                builder.Append("<Timestamp>").Append(SecurityElement.Escape(Timestamp)).Append("</Timestamp>");
            if (detail)
            {
                builder.Append("<ProcessId>").Append(SecurityElement.Escape(ProcessId)).Append("</ProcessId>");
                builder.Append("<ThreadId>").Append(SecurityElement.Escape(ThreadId)).Append("</ThreadId>");
                builder.Append("<Source>").Append(SecurityElement.Escape(Source)).Append("</Source>");
                builder.Append("<Callstack>").Append(SecurityElement.Escape(Callstack)).Append("</Callstack>");
                builder.Append("<RelatedActivityId>").Append(RelatedActivityId).Append("</RelatedActivityId>");
                builder.Append("<LogicalOperations>").Append(SecurityElement.Escape(LogicalOperations)).Append("</LogicalOperations>");
            }
            builder.Append("</Trace>");
        }

        /// <summary>インデックスを取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Index")]
        [PropertyIndex(0)]
        public int Index
        {
            get
            {
                if (IsNull(_tableTraceData.IndexColumn))
                    return 0;
                return (int)this[_tableTraceData.IndexColumn];
            }
            set { this[_tableTraceData.IndexColumn] = value; }
        }
        /// <summary>IDを取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Id")]
        [PropertyIndex(1)]
        public int Id
        {
            get
            {
                if (IsNull(_tableTraceData.IdColumn))
                    return 0;
                return (int)this[_tableTraceData.IdColumn];
            }
            set { this[_tableTraceData.IdColumn] = value; }
        }
        /// <summary>イベントを取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "EventType")]
        [PropertyIndex(2)]
        public TraceEventType EventType
        {
            get
            {
                if (IsNull(_tableTraceData.EventTypeColumn))
                    return 0;
                return (TraceEventType)this[_tableTraceData.EventTypeColumn];
            }
            set { this[_tableTraceData.EventTypeColumn] = value; }
        }
        /// <summary>メッセージを取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Message")]
        [PropertyIndex(3)]
        public string Message
        {
            get
            {
                if (IsNull(_tableTraceData.MessageColumn))
                    return null;
                return (string)this[_tableTraceData.MessageColumn];
            }
            set { this[_tableTraceData.MessageColumn] = value; }
        }
        /// <summary>詳細を取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Detail")]
        [PropertyIndex(4)]
        public string Detail
        {
            get
            {
                if (IsNull(_tableTraceData.DetailColumn))
                    return null;
                return (string)this[_tableTraceData.DetailColumn];
            }
            set { this[_tableTraceData.DetailColumn] = value; }
        }
        /// <summary>日付を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "DateTime")]
        [PropertyIndex(5)]
        public string DateTime
        {
            get
            {
                if (IsNull(_tableTraceData.DateTimeColumn))
                    return null;
                return (string)this[_tableTraceData.DateTimeColumn];
            }
            set { this[_tableTraceData.DateTimeColumn] = value; }
        }
        /// <summary>時刻を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Timestamp")]
        [PropertyIndex(6)]
        public string Timestamp
        {
            get
            {
                if (IsNull(_tableTraceData.TimestampColumn))
                    return null;
                return (string)this[_tableTraceData.TimestampColumn];
            }
            set { this[_tableTraceData.TimestampColumn] = value; }
        }
        // フッタオブジェクト
        /// <summary>プロセスIDを取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "ProcessId")]
        [PropertyIndex(7)]
        public string ProcessId
        {
            get
            {
                if (IsNull(_tableTraceData.ProcessIdColumn))
                    return null;
                return (string)this[_tableTraceData.ProcessIdColumn];
            }
            set { this[_tableTraceData.ProcessIdColumn] = value; }
        }
        /// <summary>スレッドIDを取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "ThreadId")]
        [PropertyIndex(8)]
        public string ThreadId
        {
            get
            {
                if (IsNull(_tableTraceData.ThreadIdColumn))
                    return null;
                return (string)this[_tableTraceData.ThreadIdColumn];
            }
            set { this[_tableTraceData.ThreadIdColumn] = value; }
        }
        // ヘッダオブジェクト
        /// <summary>ソースを取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Source")]
        [PropertyIndex(9)]
        public string Source
        {
            get
            {
                if (IsNull(_tableTraceData.SourceColumn))
                    return null;
                return (string)this[_tableTraceData.SourceColumn];
            }
            set { this[_tableTraceData.SourceColumn] = value; }
        }
        /// <summary>呼出し履歴を取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "Callstack")]
        [PropertyIndex(10)]
        public string Callstack
        {
            get
            {
                if (IsNull(_tableTraceData.CallstackColumn))
                    return null;
                return (string)this[_tableTraceData.CallstackColumn];
            }
            set { this[_tableTraceData.CallstackColumn] = value; }
        }
        /// <summary>関連アクティビティを示すグローバル一意識別子 (GUID: Globally Unique Identifier) を取得・設定します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "RelatedActivityId")]
        public Guid RelatedActivityId
        {
            get
            {
                if (IsNull(_tableTraceData.RelatedActivityIdColumn))
                    return Guid.Empty;
                return (Guid)this[_tableTraceData.RelatedActivityIdColumn];
            }
            set { this[_tableTraceData.RelatedActivityIdColumn] = value; }
        }
        /// <summary>論理操作スタックを取得します。</summary>
        [DisplayNameFromResource(typeof(Properties.TraceDataDisplayName), "LogicalOperations")]
        public string LogicalOperations
        {
            get
            {
                if (IsNull(_tableTraceData.LogicalOperationsColumn))
                    return null;
                return (string)this[_tableTraceData.LogicalOperationsColumn];
            }
            set { this[_tableTraceData.LogicalOperationsColumn] = value; }
        }

        /// <summary>詳細情報として、<see cref="System.Object"/> を設定します。</summary>
        /// <param name="data">詳細情報として設定する <see cref="System.Object"/>。</param>
        internal void SetDetailData(object data)
        {
            Detail = data != null ? data.ToString() : string.Empty;
        }

        /// <summary>詳細情報として、<see cref="System.Object"/> を設定します。</summary>
        /// <param name="data">詳細情報として設定する <see cref="System.Object"/> の配列。</param>
        internal void SetDetailData(params object[] data)
        {
            var sb = new StringBuilder();
            bool first = true;
            if (data != null)
            {
                for (int i = 0; i < data.Length; i++)
                {
                    if (data[i] != null)
                    {
                        if (!first)
                            sb.Append(", ");

                        sb.Append(data[i].ToString());
                        first = false;
                    }
                }
            }
            Detail = sb.ToString();
        }

        internal void SetMessageData(string format, params object[] data)
        {
            Message = data == null ? format : string.Format(CultureInfo.InvariantCulture, format, data);
        }

        /// <summary>フッタオブジェクトを設定します。</summary>
        /// <param name="options">トレースで出力するべき値を定義する <see cref="TraceOptions"/> オブジェクトを指定します。</param>
        /// <param name="eventCache">トレースで出力する内容を示す <see cref="TraceEventCache"/> オブジェクトを指定します。</param>
        internal void SetEventCache(TraceOptions options, TraceEventCache eventCache)
        {
            if ((options & TraceOptions.ProcessId) == TraceOptions.ProcessId)
                ProcessId = eventCache.ProcessId.ToString(CultureInfo.InvariantCulture);

            if ((options & TraceOptions.LogicalOperationStack) == TraceOptions.LogicalOperationStack)
            {
                var logicalOperationStack = eventCache.LogicalOperationStack;
                var sb = new StringBuilder();
                var first = true;
                foreach (Object obj in logicalOperationStack)
                {
                    if (!first)
                        sb.Append(", ");
                    else
                        first = false;

                    sb.Append(obj.ToString());
                }
                sb.AppendLine();
                LogicalOperations = sb.ToString();
            }

            if ((options & TraceOptions.ThreadId) == TraceOptions.ThreadId)
                ThreadId = eventCache.ThreadId;
            if ((options & TraceOptions.DateTime) == TraceOptions.DateTime)
                DateTime = eventCache.DateTime.ToString("o", CultureInfo.InvariantCulture);
            if ((options & TraceOptions.Timestamp) == TraceOptions.Timestamp)
                Timestamp = eventCache.Timestamp.ToString(CultureInfo.InvariantCulture);
            if ((options & TraceOptions.Callstack) == TraceOptions.Callstack)
                Callstack = eventCache.Callstack;
        }

        #region IComparable<TraceData> メンバー

        /// <summary><see cref="TraceData"/> オブジェクトを比較します。</summary>
        /// <param name="other">比較対象の <see cref="TraceData"/> オブジェクト。</param>
        /// <returns>このオブジェクトよりも比較対象が大きい場合には <c>1</c>。等しい場合には <c>0</c>。それ以外の場合は <c>false</c>。</returns>
        public int CompareTo(TraceData other)
        {
            return other == null ? 1 : Index.CompareTo(other.Index);
        }

        /// <summary><see cref="TraceData"/> オブジェクトを比較します。</summary>
        /// <param name="obj">比較対象の <see cref="TraceData"/> オブジェクト。</param>
        /// <returns>このオブジェクトよりも比較対象が大きい場合には <c>1</c>。等しい場合には <c>0</c>。それ以外の場合は <c>false</c>。</returns>
        public int CompareTo(object obj)
        {
            return CompareTo(obj as TraceData);
        }

        #endregion
    }
}